import {Matrix} from "@/components/comm/matrix";
import {Block} from "@/components/tetris/Block";
import {range, rangeArray, randomChoice, randomInt, Position} from "@/components/comm/utils";

export class Board {
    private data: Matrix<boolean>;
    private block: Block;
    private nextBlock: Block;
    private canSwap: boolean = true;
    private gameOverCallback?: () => void;
    private blockShapes: Block[] = [
        new Block([-2, 0], [[-1, 0], [-2, -1], [-2, 0], [-2, 1]], 'red'),
        new Block([-2.5, -0.5], [[-2, -1], [-2, 0], [-3, -1], [-3, 0]], 'orange'),
        new Block([-3, 0], [[-1, 0], [-2, 0], [-3, 0], [-4, 0]], 'yellow'),
        new Block([-2.5, -0.5], [[-2, -1], [-3, -1], [-3, 0], [-4, 0]], 'green'),
        new Block([-2.5, -0.5], [[-2, 0], [-3, -1], [-3, 0], [-4, -1]], 'blue'),
        new Block([-2, 0], [[-1, -1], [-2, -1], [-2, 0], [-2, 1]], 'cyan'),
        new Block([-2, 0], [[-1, 1], [-2, -1], [-2, 0], [-2, 1]], 'purple'),
    ];
    public score: number = 0;
    public isGameOver: boolean = false;

    public constructor(readonly xs: number, readonly ys: number) {
        this.data = Matrix.from(xs, ys, () => false);
        this.blockShapes.forEach(block => block.move(xs, Math.floor(ys / 2)));
        this.block = this.createNext();
        this.nextBlock = this.createNext();
    }

    public getColor(x: number, y: number): string {
        if (this.block.contains([x, y])) {
            return this.block.color;
        } else if (this.data.get([x, y], true)) {
            return 'grey';
        } else {
            return 'white';
        }
    }

    public getNextColor(x: number, y: number): string {
        x += Math.min(...this.nextBlock.children.map(p => p[0]));
        y += Math.min(...this.nextBlock.children.map(p => p[1]));
        if (this.nextBlock.contains([x, y])) {
            return this.nextBlock.color;
        } else if (this.canSwap) {
            return 'white';
        } else {
            return 'grey';
        }
    }

    private checkOverlap() {
        for (const pos of this.block.children) {
            if (this.data.get(pos, true)) {
                return true;
            }
        }
        return false;
    }

    private tryBlockOp(fn: () => void) {
        const blockClone = this.block.clone();
        fn();
        if (this.checkOverlap()) {
            this.block = blockClone;
            return false;
        }
        return true;
    }

    public tryMove(x: number, y: number) {
        return this.tryBlockOp(() => this.block.move(x, y));
    }

    public tryRotateLeft() {
        return this.tryBlockOp(() => this.block.rotateLeft());
    }

    public tryRotateRight() {
        return this.tryBlockOp(() => this.block.rotateRight());
    }

    private createNext(): Block {
        const next = randomChoice(this.blockShapes).clone();
        rangeArray(randomInt(0, 3)).forEach(() => next.rotateRight());
        return next;
    }

    private create() {
        this.block = this.nextBlock;
        this.nextBlock = this.createNext();
        if (this.checkOverlap()) {
            this.gameOver();
        }
    }

    private eliminate() {
        let dx = 0;
        for (const x of range(this.data.rows)) {
            while (rangeArray(this.data.cols).every(y => this.data.get([x + dx, y], false))) {
                dx++;
            }
            for (const y of range(this.data.cols)) {
                this.data.set([x, y], this.data.get([x + dx, y], false));
            }
        }
        this.score += dx;
    }

    private fix() {
        for (const pos of this.block.children) {
            this.data.set(pos, true);
        }
        this.eliminate();
        this.canSwap = true;
    }

    public full(): void {
        if (this.tryMove(-1, 0)) {
            return;
        }
        this.fix();
        this.create();
    }

    public quickFull(): void {
        while (this.tryMove(-1, 0)) {
        }
        this.fix();
        this.create();
    }

    public swap(): void {
        if (!this.canSwap) {
            return;
        }
        [this.block, this.nextBlock] = [this.nextBlock, this.block];
        const nextShape = this.blockShapes.find(b => b.color === this.nextBlock.color)!;
        this.nextBlock.moveTo(nextShape.center[0], nextShape.center[1]);
        this.canSwap = false;
    }

    public onGameOver(fn: () => void): void {
        this.gameOverCallback = fn;
    }

    private gameOver() {
        this.isGameOver = true;
        this.gameOverCallback?.();
    }
}